/*
 * Copyright 2008-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.faxsun.controller.cart;

import com.faxsun.core.offer.service.FSOfferService;
import com.faxsun.core.order.service.FSReferralCustomerOrderItemConstants;
import com.faxsun.core.order.service.exception.TreasureDisqualifiedException;
import com.faxsun.core.web.checkout.model.FSShippingInfoForm;
import org.apache.commons.lang3.StringUtils;
import org.broadleafcommerce.core.catalog.domain.Product;
import org.broadleafcommerce.core.inventory.service.InventoryUnavailableException;
import org.broadleafcommerce.core.offer.domain.Offer;
import org.broadleafcommerce.core.offer.domain.OfferCode;
import org.broadleafcommerce.core.offer.service.exception.OfferMaxUseExceededException;
import org.broadleafcommerce.core.order.domain.NullOrderImpl;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.service.exception.*;
import org.broadleafcommerce.core.pricing.service.exception.PricingException;
import org.broadleafcommerce.core.web.checkout.model.OrderInfoForm;
import org.broadleafcommerce.core.web.controller.cart.BroadleafCartController;
import org.broadleafcommerce.core.web.order.CartState;
import org.broadleafcommerce.core.web.order.model.AddToCartItem;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/cart")
public class CartController extends BroadleafCartController {

	protected static String checkoutView = "checkout/checkout";
	protected static String checkoutPageRedirect = "redirect:/checkout";

	@Override
	@RequestMapping("")
	public String cart(HttpServletRequest request, HttpServletResponse response, Model model)
			throws PricingException {
		return super.cart(request, response, model);
	}

	/*
	 * The Heat Clinic does not show the cart when a product is added. Instead,
	 * when the product is added via an AJAX POST that requests JSON, we only
	 * need to return a few attributes to update the state of the page. The most
	 * efficient way to do this is to call the regular add controller method,
	 * but instead return a map that contains the necessary attributes. By using
	 * the @ResposeBody tag, Spring will automatically use Jackson to convert
	 * the returned object into JSON for easy processing via JavaScript.
	 */
	@RequestMapping(value = "/add", produces = "application/json")
	public @ResponseBody Map<String, Object> addJson(HttpServletRequest request,
			HttpServletResponse response, Model model,
			@ModelAttribute("addToCartItem") AddToCartItem addToCartItem) throws IOException,
			PricingException, AddToCartException {
		Map<String, Object> responseMap = new HashMap<String, Object>();
		try {
			String referralCode = request.getParameter(FSReferralCustomerOrderItemConstants.REFERRAL_CODE_PARAM);
            if (StringUtils.isNotEmpty(referralCode)) {
                addToCartItem.getItemAttributes().put(FSReferralCustomerOrderItemConstants.REFERRAL_CODE, referralCode);
            }

			super.add(request, response, model, addToCartItem);

			if (addToCartItem.getItemAttributes() == null || addToCartItem.getItemAttributes().size() == 0) {
				responseMap.put("productId", addToCartItem.getProductId());
			}
			responseMap.put("productName", catalogService.findProductById(addToCartItem.getProductId())
					.getName());
			responseMap.put("quantityAdded", addToCartItem.getQuantity());
			responseMap.put("cartItemCount", String.valueOf(CartState.getCart().getItemCount()));
			if (addToCartItem.getItemAttributes() == null || addToCartItem.getItemAttributes().size() == 0) {
				// We don't want to return a productId to hide actions for when
				// it is a product that has multiple
				// product options. The user may want the product in another
				// version of the options as well.
				responseMap.put("productId", addToCartItem.getProductId());
			}
		} catch (AddToCartException e) {
			if (e.getCause() instanceof RequiredAttributeNotProvidedException) {
				responseMap.put("error", "allOptionsRequired");
			} else if (e.getCause() instanceof ProductOptionValidationException) {
				ProductOptionValidationException exception = (ProductOptionValidationException) e.getCause();
				responseMap.put("error", "productOptionValidationError");
				responseMap.put("errorCode", exception.getErrorCode());
				responseMap.put("errorMessage", exception.getMessage());
				// blMessages.getMessage(exception.get, locale))
			} else if (e.getCause() instanceof TreasureDisqualifiedException
					|| e.getCause().getCause() instanceof TreasureDisqualifiedException) {
				responseMap.put("error", "treasureDisqualified");

				String message = (e.getCause() instanceof TreasureDisqualifiedException) ? e.getCause()
						.getMessage() : e.getCause().getCause().getMessage();

				responseMap.put("errorMessage", message);
			} else if (e.getCause() instanceof InventoryUnavailableException) {
				responseMap.put("error", "inventoryUnavailable");
			} else {
				throw e;
			}
		}

		return responseMap;
	}

	/*
	 * The Heat Clinic does not support adding products with required product
	 * options from a category browse page when JavaScript is disabled. When
	 * this occurs, we will redirect the user to the full product details page
	 * for the given product so that the required options may be chosen.
	 */
	@RequestMapping(value = "/add", produces = { "text/html", "*/*" })
	public String add(HttpServletRequest request, HttpServletResponse response, Model model,
			RedirectAttributes redirectAttributes,
			@ModelAttribute("addToCartItem") AddToCartItem addToCartItem) throws IOException,
			PricingException, AddToCartException {
		try {
			return super.add(request, response, model, addToCartItem);
		} catch (AddToCartException e) {
			Product product = catalogService.findProductById(addToCartItem.getProductId());
			return "redirect:" + product.getUrl();
		}
	}

	@RequestMapping("/updateQuantity")
	public String updateQuantity(HttpServletRequest request, HttpServletResponse response, Model model,
			RedirectAttributes redirectAttributes,
			@ModelAttribute("addToCartItem") AddToCartItem addToCartItem) throws IOException,
			PricingException, UpdateCartException, RemoveFromCartException {
		try {
			return super.updateQuantity(request, response, model, addToCartItem);
		} catch (UpdateCartException e) {
			if (e.getCause() instanceof TreasureDisqualifiedException
					|| e.getCause() instanceof InventoryUnavailableException) {

				String errorMessageString = e.getCause() instanceof TreasureDisqualifiedException ? e
						.getCause().getMessage() : "库存不足";

				// Since there was an exception, the order gets detached from
				// the Hibernate session. This re-attaches it
				CartState.setCart(orderService.findOrderById(CartState.getCart().getId()));
				if (isAjaxRequest(request)) {
					model.addAttribute("errorMessage", errorMessageString);
					return getCartView();
				} else {
					redirectAttributes.addAttribute("errorMessage", errorMessageString);
					return getCartPageRedirect();
				}
			} else {
				throw e;
			}
		}
	}

	@Override
	@RequestMapping("/remove")
	public String remove(HttpServletRequest request, HttpServletResponse response, Model model,
			@ModelAttribute("addToCartItem") AddToCartItem addToCartItem) throws IOException,
			PricingException, RemoveFromCartException {
		Order cart = CartState.getCart();

		if (CartState.getCart() instanceof NullOrderImpl) {
			if (isAjaxRequest(request)) {
				Map<String, Object> extraData = new HashMap<String, Object>();
				extraData.put("cartItemCount", cart.getItemCount());
				extraData.put("productId", addToCartItem.getProductId());
				model.addAttribute("blcextradata", new ObjectMapper().writeValueAsString(extraData));
				return getCartView();
			} else {
				return getCartPageRedirect();
			}
		}
		return super.remove(request, response, model, addToCartItem);
	}

	@Override
	@RequestMapping("/empty")
	public String empty(HttpServletRequest request, HttpServletResponse response, Model model)
			throws PricingException {
		// return super.empty(request, response, model);
		return "ajaxredirect:/";

	}

	@RequestMapping("/promo")
	public String addPromoCode(HttpServletRequest request, HttpServletResponse response, Model model,
			@RequestParam("promoCode") String customerOffer, RedirectAttributes redirectAttributes,
			@ModelAttribute("orderInfoForm") OrderInfoForm orderInfoForm,
			@ModelAttribute("shippingInfoForm") FSShippingInfoForm shippingForm) throws IOException,
			PricingException {
		Order cart = CartState.getCart();

		Boolean promoAdded = false;
		String exception = "";

		if (cart != null && !(cart instanceof NullOrderImpl)) {
			final OfferCode offerCode = offerService.lookupOfferCodeByCode(customerOffer);
			if (offerCode != null) {
				try {
					if (offerService instanceof FSOfferService) {
						FSOfferService fsOfferService = (FSOfferService) offerService;
						if (fsOfferService.couldOffersApplyToOrder(new ArrayList<Offer>() {
							private static final long serialVersionUID = 1L;
							{
								add(offerCode.getOffer());
							}
						}, cart)) {
							orderService.addOfferCode(cart, offerCode, false);
							promoAdded = true;
							cart = orderService.save(cart, true);
						} else {
							exception = PromotionConstants.CODE_NOT_APPLICABLE;
						}
					}
				} catch (OfferMaxUseExceededException e) {
					exception = PromotionConstants.USE_LIMIT_EXCEEDED;
				}
			} else {
				exception = PromotionConstants.INVALID_CODE;
			}
		} else {
			exception = PromotionConstants.INVALID_CART;
		}

		if (isAjaxRequest(request)) {
			Map<String, Object> extraData = new HashMap<String, Object>();
			extraData.put("promoAdded", promoAdded);
			extraData.put("exception", exception);
			model.addAttribute("blcextradata", new ObjectMapper().writeValueAsString(extraData));
			return getCheckoutView();
		} else {
			redirectAttributes.addFlashAttribute("promoAdded", promoAdded);
			redirectAttributes.addFlashAttribute("exception", exception);
			return getCheckoutPageRedirect();
		}
	}

	@Override
	@RequestMapping("/promo/remove")
	public String removePromo(HttpServletRequest request, HttpServletResponse response, Model model,
			@RequestParam("offerCodeId") Long offerCodeId) throws IOException, PricingException {
		Order cart = CartState.getCart();

		OfferCode offerCode = offerService.findOfferCodeById(offerCodeId);

		orderService.removeOfferCode(cart, offerCode, false);
		cart = orderService.save(cart, true);

		return isAjaxRequest(request) ? getCheckoutView() : getCheckoutPageRedirect();
	}

	public String getCheckoutView() {
		return checkoutView;
	}

	public String getCheckoutPageRedirect() {
		return checkoutPageRedirect;
	}

}
